GraphQL is a protocol, invented by Facebook engineers. It was released to the public in 2015 and is now developed by an independent foundation. It is a direct competitor to protocols like REST or SOAP. Even if it is agnostic regarding the transport layer, it is usually used to communicate between a browser and a server on top of a HTTPS connection. The key differentiator with GraphQL is that the clients are responsible for asking the complete list of fields they want. This is a paradigm shift compared to existing technology. Clients can specify the shape of the answer they expect using a “query language”. Let’s assume we want to write a blogging platform. A query listing articles might look like this (Listing 1).
The query above will fetch articles with the requested list of fields. The server will serve a JSON response that matches the requested fields (Listing 2).
Note how GraphQL allows you to explore the response in depth: you can fetch details about the author of an article without running a second query! If another view requires different fields, you can write a different query with another set of fields:
query { articles(search: "php") { id title logo tags(limit: 10) } }
We can adapt the fields returned by the API from the client side, without touching code on the server side. This greatly reduces friction between front-end and back-end developers. This is also an overwhelming advantage over existing technology. With GraphQL, there is no over-fetching or under-fetching. You require exactly what you want. However, this freedom comes with its own set of risks. Because a client can request the list of fields it wants, an evil user could easily write a query requiring thousands of fields to cause a denial of service attack on your server. Most libraries come with ways to limit query complexity, but this is something new the developer needs to deal with. In that regard, GraphQL applications are easier to write for intranets / extranets (where the user is logged and therefore traceable) than for public websites.
GraphQL is not only about the client shaping its query. There are a lot of other very interesting features. My favorite two are introspection and strict typing. The list of fields a client can query comes from a “schema”. The schema is a list of “types” (similar to classes in PHP). Each type contains a list of “fields”. GraphQL comes with a syntax to describe this schema. The schema for the example above would typically look like this (Listing 3).
Depending on the library you use, you will have to write this schema yourself, or the schema will be hidden and handled internally by your library.
query { articles(search: "php") { id title body author { id name } } }
{ "data": [ { "id": "4212BEAF", "title": "GraphQL rocks", "body": "Hello world", "author": { "id": "BFAC12", "name": "David", } } ] }
The PHP ecosystem
GraphQL is only a protocol. It needs a client (in the browser) and a server to talk to. In the browser, you can write a direct Ajax request to your server, but most of the time, you will want to use a GraphQL client to ease this part. There are 2 major players here:
- Relay, written by Facebook but dedicated to React
- and Apollo who has binding libraries for all major JS frameworks out there.
On the server-side, Facebook used to provide a reference implementation in NodeJS and you will find plenty of GraphQL servers in the JS land. Hopefully, PHP now has its fair share of GraphQL servers too! In the PHP land, there is one dominant low level library: webonyx/graphql-php. This library is in charge of parsing GraphQL queries and schemas and resolving the requests.
type Query { articles(search: String): [Article!]! } type Article { id: ID title: String! body: String! logo: String author: Author! tags: [String!]! } type Author { id: ID name: String! }
namespace App\Controller; use TheCodingMachine\GraphQLite\Annotations\Query; class ArticleController { // ... /** * @Query * @return Article[] */ function getArticles(?string $search): array { return $this->repository->findByText($search); } }
It is extremely powerful, close to the GraphQL specifications, but is also very verbose and not extremely developer friendly. If you want to add GraphQL features to an existing framework or CMS, webonyx/graphql-php is what you need. On the other hand, if you are developing a web application, you will want to try something a bit more easy to use. Many libraries have been written to ease the work with GraphQL (and they all use Webonyx’s library under the hood). A good high-level library will help you to not repeat yourself. Indeed, the list of fields attached to a type is usually already described in the PHP class, or in the database model. When you create a new field, you don’t want to have to add it at three different places. GraphQL libraries can solve this problem using 2 different approaches:
- Schema first: You write the GraphQL schema, and the library will infer the bindings to the model automatically. Libraries like “Lighthouse” for Laravel or “Overblog’s GraphqlBundle” for Symfony use this schema first technique.
- Code first: You code your PHP classes, put some annotations and the library will infer the GraphQL schema from the code. Libraries like API Platform (for Symfony) or GraphQLite (framework agnostic) are in this category.
Let’s now dive a bit and have a closer look at one of these contenders: GraphQLite.
GraphQLite
GraphQLite is built on top of webonyx/graphql-php. It uses reflection to analyze your PHP methods and map them to a GraphQL schema automatically. Writing a query with GraphQLite is straightforward (Listing 4).
The GraphQL queries can be organized in “controllers”. When you want a PHP method to be treated as a GraphQL query, you simply add the @Query annotation on the method and you are done! Since GraphQL is strongly typed, your PHP method needs to be strongly typed. But PHP has a few shortcomings when it comes to typing. For instance, it does not support “generics” natively so you cannot express the allowed types in an array. In those cases, GraphQLite will look for more information in the Docblock. Notice how the return type of “getArticles” (“array”) is narrowed down to Article[].
If you try to run this query in GraphQLite, you will be greeted with… an error message!
For return type of App\GraphqlController\ArticleController::getArticles, cannot map class “App\Entity\Article” to a known GraphQL type.
This message means that GraphQLite does not know how to map our “Article” PHP class to a GraphQL type. It will not do this automatically (that would be a security risk), we need to explicitly tell GraphQLite what class is allowed to be exposed as a GraphQL type and which properties are exposed as fields. We do this through annotations too (Listing 5).
The code above will automatically create a GraphQL type. In GraphQL type language, it would look like this:
type Article { title: String! author: Author! }
Of course, since the “Author” class is also used in a GraphQL field, we must turn it into a type by also putting a @Type annotation on the PHP class. Now, everywhere in your code, the Article PHP class will be automatically mapped to the Article GraphQL type.
Autowiring services in field resolvers
Let’s modify our example a bit. What if the articles are translated in many languages? I would probably need to use a “Translator” class to fetch all the possible translations of my article.
But I cannot inject the “Translator” service into the “Article” class because “Article” is a model, not a service. This is where autowiring comes into play.
/** * @Field() * @Autowire(for="$translator") */ public function getTitle(TranslatorInterface $translator): string { return $translator->trans('article_title_'.$this->id); }
Thanks to the @Autowire annotation, GraphQLite will inject a translation service in the $translator parameter. So any time you run a GraphQL query on the Article type, GraphQLite will inject the services for you:
{ articles { title } }
namespace App\Entity; use TheCodingMachine\GraphQLite\Annotations\Field; use TheCodingMachine\GraphQLite\Annotations\Type; /** * @Type() */ class Article { // ... /** * @Field() */ public function getTitle(): string { return $this->name; } /** * @Field() */ public function getAuthor(): Author { return $this->author; } }
If you are a Symfony or Laravel user, this feature will remind you how you can inject services in controller actions. But in the case of GraphQLite, you can also inject services in models! And this has the potential of completely changing the way you think about your application. It pushes you in organizing your code in a fashion that looks a lot like Domain Driven Design. In the example above, a title clearly belongs to an article, and if we need an additional service to be able to retrieve it, it makes sense to pass the service as an argument to the getTitle method. Compare that to performing the translation outside the model. In a REST approach, it is terribly easy to write something like:
return new JsonResponse([ 'name' => $this->translateService->trans('article_title_'.$product->getId()), ]);
If you write your code like this, the notion of “article title” becomes external to the Article class. Looking at the Article class code only, a developer does not even know that it “has” a name.
By using autowiring, we get the opportunity to keep all our fields in the same class and it usually makes sense!
Managing access right
In a classic REST API, access granting is usually handled at the controller level. In a GraphQL application, since the client can design its own query, we need not only to check access granting at the controller level, but also for each field. This means we are pushing the authorization layer in the domain. Hopefully, it usually makes sense. Let’s go back to our example and implement an access right. For our blog platform, an article body should be visible only if it is published. If it is not published, only the author can see it. In GraphQLite, we do this using the @Security annotation.
class Article { /** * @Field * @Security("this.isPublished() || user == this.getAuthor()") */ public function getBody(): string { // ... } }
Have a look at the code inside the @Security annotation. This is not PHP, this is not Javascript. If you are used to Symfony, you might have recognized the Symfony Expression Language. A number of variables are accessible in the context of this code. For instance, “user” refers to the currently logged user, while “this” refers obviously to the current model. Also, this @Security annotation can be used in any project. It is not a Symfony-only feature.
Conclusion
I only introduced a few features of GraphQLite. There are many more to learn about but I specifically chose the features that can have a positive impact on the way you think about your code.
I hope this article raised your interest in GraphQL. Give it a try!